#pragma once

#include <stdexcept>

struct git_reference;
struct git_repository;
struct git_remote;
struct git_annotated_commit;

namespace git
{

namespace detail
{

class PtrHolderFreeFunctions
{
public:
    static void free(git_reference *ptr);

    static void free(git_repository *ptr);

    static void free(git_remote *ptr);

    static void free(git_annotated_commit *ptr);

private:
    PtrHolderFreeFunctions() {}
};

template <typename PtrType>
class PtrHolder
{
public:
    class AlreadyVisitedError : public std::logic_error
    {
    public:
        AlreadyVisitedError() : std::logic_error("PtrHolder can be only visited once") {}
        virtual ~AlreadyVisitedError() {}
    };

    class AlreadyTakedError : public std::logic_error
    {
    public:
        AlreadyTakedError() : std::logic_error("PtrHolder can be only taked once") {}
        virtual ~AlreadyTakedError() {}
    };

    PtrHolder() {}

    PtrHolder(PtrType *ptr)
        : visited_(true), ptr_(ptr) {
    }

    PtrHolder(const PtrHolder &) = delete;

    PtrHolder(PtrHolder &&) = delete;

    ~PtrHolder() {
        if (ptr_) {
            PtrHolderFreeFunctions::free(ptr_);
            ptr_ = nullptr;
        }
    }

    PtrHolder &operator=(const PtrHolder &) = delete;

    PtrHolder &operator=(PtrHolder &&) = delete;

    operator bool() const {
        return ptr_ != nullptr;
    }

    PtrType *look() const {
        return ptr_;
    }

    PtrType **visit() {
        if (visited_) {
            throw AlreadyVisitedError();
        }
        visited_ = true;
        return &ptr_;
    }

    PtrType *take() {
        if (taked_) {
            throw AlreadyTakedError();
        }
        PtrType *p = ptr_;
        ptr_ = nullptr;
        taked_ = true;
        return p;
    }

private:
    bool visited_ = false;
    bool taked_ = false;
    PtrType *ptr_ = nullptr;
};

} // namespace detail

using RefHolder = detail::PtrHolder<git_reference>;
using RepoHolder = detail::PtrHolder<git_repository>;
using RemoteHolder = detail::PtrHolder<git_remote>;
using AnnotatedCommitHolder = detail::PtrHolder<git_annotated_commit>;

} // namespace git
